package com.powernode.filter;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.powernode.config.WhileUrlsConfig;
import com.powernode.constant.GatewayConstant;
import com.powernode.constant.HttpConstant;
import com.powernode.constant.ResponseCode;
import com.powernode.model.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.LocalDate;
import java.util.List;

/**
 * @author syo 2319615164@qq.com
 * @description GateWay的全局过滤器，用于过滤检验Token
 * @date 2024/3/31
 * @Copyright syo
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtCheckFilter implements GlobalFilter, Ordered {

    private final StringRedisTemplate stringRedisTemplate;

    private final WhileUrlsConfig whileUrlsConfig;

    /**
     * 判断url是否在白名单
     * @return 是否能够放行
     */
    private boolean isAllow(String url) {
        return whileUrlsConfig.getAllowUrls().contains(url);
    }

    /**
     * 校验Token
     * 1.拿到路径
     * 2.判断需要放行的路径
     * 3.拿到请求头
     * 4.获取token
     * 5.判断token
     * 6.拦截等
     * @param exchange      上下文环境
     * @param chain         请求链
     * @return              Mono
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 拿到请求
        ServerHttpRequest request = exchange.getRequest();

        // 2. 获取请求路径
        String path = request.getURI().getPath();

        // 3. 判断当前请求路径是否需要放行通过（白名单路径，用户不需要登录）
        if (isAllow(path)) {

            // 3.1放行
            return chain.filter(exchange);
        }

        // 4. 当前请求路径需要校验,即校验请求头中token值
        // 5. 获取请求头中的token值
        List<String> auths = request.getHeaders().get(GatewayConstant.AUTHORIZATION);

        // 6. 判断是否有token
        if (auths != null && CollectionUtil.isNotEmpty(auths)) {

            // 6.1 有值则获取集合中的第1个
            // token值的格式：bearer jwt
            String token = auths.get(0);

            // 6.2 判断token是否有值
            if (StrUtil.isNotBlank(token)) {

                // 6.3 截取token值中的jwt
                String jwt = token
                        .replaceFirst(GatewayConstant.BEARER, "");

                // 6.4 判断jwt是否为空并且是否为我们系统颁发的jwt
                if (StrUtil.isNotBlank(jwt)
                        && Boolean.TRUE
                        .equals(stringRedisTemplate
                                .hasKey(GatewayConstant.TOKEN_PREFIX + jwt))) {

                    // 6.5 放行
                    return chain.filter(exchange);
                }
            }
        }

        // 7. 拦截未授权的请求
        log.error("拦截非法请求，时间：{}，请求路径：{}", LocalDate.now(), path);

        // 8. 获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().add(HttpConstant.CONTENT_TYPE, HttpConstant.APPLICATION_TYPE);

        // 9. 组装业务返回值
        Result<Object> res = Result.fail(ResponseCode.UN_AUTHORIZATION, null);

        // 10. 创建转换器
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(res);
        } catch (JsonProcessingException e) {
            log.error("Json转换异常！", e);
        }

        // 11. 返回拦截结果
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(dataBuffer));
    }

    /**
     * 拦截器级别，级别越小越先执行
     * @return 0
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
